home *** CD-ROM | disk | FTP | other *** search
- /****************************************************************************/
- /****************************************************************************/
- /** **/
- /** Connect-4 Algorithm **/
- /** **/
- /** By Keith Pomakis **/
- /** (kppomaki@jeeves.uwaterloo.ca) **/
- /** **/
- /** Fall, 1993 **/
- /** **/
- /****************************************************************************/
- /** **/
- /** This file provides the functions necessary to implement a front-end- **/
- /** independent, device-independent Connect-4 game. Multiple board sizes **/
- /** are supported. It is also possible to specify the number of pieces **/
- /** necessary to connect in a row in order to win. Therefore one can **/
- /** play Connect-3, Connect-5, etc. An efficient tree-searching **/
- /** algorithm (making use of alpha-beta cutoff decisions) has been **/
- /** implemented to insure that the computer plays as quickly as possible. **/
- /** **/
- /** The declaration of the public functions necessary to use this file **/
- /** are contained in "c4.h". **/
- /** **/
- /** In all of the public functions, the value of player can be any **/
- /** integer, where an even integer refers to player 0 and an odd integer **/
- /** refers to player 1. **/
- /** **/
- /****************************************************************************/
- /****************************************************************************/
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <time.h>
- #include "c4.h"
-
- /* The static global variables required. */
-
- #define NUM_OF_TEMP_STATES 42
- static long size_x, size_y, num_to_connect;
- static long win_places;
- static Boolean ***map;
- static Game_state real_state;
- static Boolean game_in_progress = FALSE, seed_chosen = FALSE;
- static void (*poll_function)(void) = NULL;
- static long poll_level;
- static Game_state temp_states[NUM_OF_TEMP_STATES];
- static Boolean temp_array[NUM_OF_TEMP_STATES];
- static long temp_states_allocated = 0;
-
- /* Some macros for convenience. */
-
- #define other(x) (((x)==1)? 0 : 1)
- #define real_player(x) (x & 1)
-
- /* A declaration of the local functions. */
-
- static void insure_game(void);
- static long num_of_win_places(long x, long y, long n);
- static void update_score(Game_state *state, long player, long x, long y);
- static Boolean drop_piece(Game_state *state, long player, long column);
- static long player_score(Game_state *state, long player);
- static Boolean winner(Game_state *state, long player);
- static Boolean tie(Game_state *state);
- static long goodness_of(Game_state *state, long player);
- static Game_state *copy_state(Game_state *state);
- static void destroy_state(Game_state *state);
- static long worst_goodness(Game_state *state, long player, long level,
- long depth, long so_far);
- static void *emalloc(unsigned int n);
-
-
- /****************************************************************************/
- /** **/
- /** This function specifies that the computer should call the specified **/
- /** function from time to time, in essence polling it to see if the **/
- /** front-end interface requires any attention. The specified function **/
- /** should accept void and return void. level is the level of lookahead **/
- /** at which the function should be called. This level is measured from **/
- /** the bottom. Eg. If the lookahead level is set to 6 and level is set **/
- /** to 4, with a 7x6 board, this function will be called a maximum of **/
- /** 7^2 = 49 times (once for each (6-4)th = 2nd level node visited. **/
- /** **/
- /** Note that if a node is not visited due to apha-beta cutoff, this **/
- /** function will not be called at that node. Therefore only a maximum **/
- /** number of calls can be predicted (with a minimum of 1). **/
- /** **/
- /** If no polling is required, the polling function can be specified as **/
- /** NULL. This is the default. This function can be called an arbitrary **/
- /** number of times throughout any game. **/
- /** **/
- /****************************************************************************/
-
- void
- poll(void (*poll_func)(void), long level)
- {
- poll_function = poll_func;
- poll_level = level;
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function sets up a new game. This must be called exactly once **/
- /** before each game is started. Before it can be called a second time, **/
- /** end_game() must be called to destroy the previous game. **/
- /** **/
- /** width and height are the desired dimensions of the game board, while **/
- /** num is the number of pieces required to connect in a row in order to **/
- /** win the game. **/
- /** **/
- /****************************************************************************/
-
- void
- new_game(long width, long height, long num)
- {
- long i, j, k, count;
-
- if (game_in_progress) {
- fprintf(stderr, "new_game: game already in progress\n");
- exit(EXIT_FAILURE);
- }
-
- if (width < 1 || height < 1 || num < 1) {
- fprintf(stderr, "new_game: invalid parameters\n");
- exit(EXIT_FAILURE);
- }
-
- size_x = width;
- size_y = height;
- num_to_connect = num;
- win_places = num_of_win_places(size_x, size_y, num_to_connect);
-
- /* Set up a random seed for making random decisions when there is */
- /* equal goodness between two moves. */
- if (!seed_chosen) {
- srand(time((time_t *)0));
- seed_chosen = TRUE;
- }
-
- /* Set up the board */
-
- real_state.board = (char **) emalloc(size_x * sizeof(char *));
- for (i=0; i<size_x; i++) {
- real_state.board[i] = (char *) emalloc(size_y * sizeof(char));
- for (j=0; j<size_y; j++)
- real_state.board[i][j] = EMPTY;
- }
-
- /* Set up the score array */
-
- real_state.score_array[0] = (long *) emalloc(win_places * sizeof(long));
- real_state.score_array[1] = (long *) emalloc(win_places * sizeof(long));
- for (i=0; i<win_places; i++) {
- real_state.score_array[0][i] = 1;
- real_state.score_array[1][i] = 1;
- }
-
- /* Set up the map */
-
- map = (Boolean ***) emalloc(size_x * sizeof(Boolean **));
- for (i=0; i<size_x; i++) {
- map[i] = (Boolean **) emalloc(size_y * sizeof(Boolean *));
- for (j=0; j<size_y; j++) {
- map[i][j] = (Boolean *) emalloc(win_places * sizeof(Boolean));
- for (k=0; k<win_places; k++)
- map[i][j][k] = FALSE;
- }
- }
-
- count = 0;
-
- /* Fill in the horizontal win positions */
- for (i=0; i<size_y; i++)
- for (j=0; j<size_x-num_to_connect+1; j++) {
- for (k=0; k<num_to_connect; k++)
- map[j+k][i][count] = TRUE;
- count++;
- }
-
- /* Fill in the vertical win positions */
- for (i=0; i<size_x; i++)
- for (j=0; j<size_y-num_to_connect+1; j++) {
- for (k=0; k<num_to_connect; k++)
- map[i][j+k][count] = TRUE;
- count++;
- }
-
- /* Fill in the forward diagonal win positions */
- for (i=0; i<size_y-num_to_connect+1; i++)
- for (j=0; j<size_x-num_to_connect+1; j++) {
- for (k=0; k<num_to_connect; k++)
- map[j+k][i+k][count] = TRUE;
- count++;
- }
-
- /* Fill in the backward diagonal win positions */
- for (i=0; i<size_y-num_to_connect+1; i++)
- for (j=size_x-1; j>=num_to_connect-1; j--) {
- /*
- for (j=size_x-1; j>=size_x-num_to_connect; j--) {
- */
- for (k=0; k<num_to_connect; k++)
- map[j-k][i+k][count] = TRUE;
- count++;
- }
-
- real_state.num_of_pieces = 0;
-
- for (i=0; i<NUM_OF_TEMP_STATES; i++)
- temp_array[i] = FALSE;
-
- game_in_progress = TRUE;
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function drops a piece of the specified player into the **/
- /** specified column. Note that column numbering starts at 0. A value **/
- /** of TRUE is returned if the drop was successful, or FALSE otherwise. **/
- /** A drop is unsuccessful if the specified column number is invalid or **/
- /** full. **/
- /** **/
- /****************************************************************************/
-
- Boolean
- make_move(long player, long column)
- {
- insure_game();
- if (column >= size_x || column < 0) return FALSE;
- return drop_piece(&real_state, real_player(player), column);
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function instructs the computer to make a move for the specified **/
- /** player. level specifies the number of levels deep the computer **/
- /** should search the game tree in order to make its decision. This **/
- /** corresponds to the number of "moves" in the game, where each player's **/
- /** turn is considered a move. The column number of the column in which **/
- /** the piece was dropped is returned. Note that column numbering starts **/
- /** at 0. If no move is possible (i.e. the game board is full), -1 is **/
- /** returned. Note that for a standard 7x6 game of Connect-4, the **/
- /** computer is brain-dead at levels of three or less, while at levels of **/
- /** 4 or more the computer provides a challenge. **/
- /** **/
- /****************************************************************************/
-
- long
- automatic_move(long player, long level)
- {
- long i, best_column, goodness, best_worst;
- Game_state *temp_state;
- long num_of_equal, real;
-
- insure_game();
- real = real_player(player);
-
- if (level < 1) {
- fprintf(stderr, "automatic_move: invalid level\n");
- exit(EXIT_FAILURE);
- }
-
- best_worst = -2000000;
- best_column = -1;
-
- /* Simulate a drop in each of the columns and see what the results are. */
-
- for (i=0; i<size_x; i++) {
- temp_state = copy_state(&real_state);
-
- /* If this column is full, ignore it as a possibility. */
- if (!drop_piece(temp_state, real, i)) {
- destroy_state(temp_state);
- continue;
- }
-
- /* If this drop wins the game, it is a really good move! */
- if (winner(temp_state, real)) {
- best_worst = 1000000;
- best_column = i;
- }
-
- /* Otherwise, look ahead to see how good this move may turn out */
- /* to be (assuming the opponent makes the best moves possible). */
- else
- goodness = worst_goodness(temp_state, real, level, 1, best_worst);
-
- /* If this move looks better than the ones previously considered, */
- /* remember it. */
- if (goodness > best_worst) {
- best_worst = goodness;
- best_column = i;
- num_of_equal = 1;
- }
-
- /* If two moves are equally as good, make a random decision. */
- if (goodness == best_worst) {
- num_of_equal++;
- if (rand()%100000 < ((float)1/(float)num_of_equal) * 100000)
- best_column = i;
- }
-
- destroy_state(temp_state);
- }
-
- /* Drop the piece in the column decided upon. */
-
- if (best_column >= 0)
- drop_piece(&real_state, real, best_column);
-
- return best_column;
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function returns the state of the current game. The Game_state **/
- /** structure returned is defined in "c4.h". **/
- /** **/
- /****************************************************************************/
-
- Game_state
- get_game_state(void)
- {
- insure_game();
- return real_state;
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function returns the "score" of the specified player. This **/
- /** score is a function of how many winning positions are still available **/
- /** to the player and how close he/she is to achieving each of these **/
- /** positions. The scores of both players can be compared to observe how **/
- /** well they are doing relative to each other. **/
- /** **/
- /****************************************************************************/
-
- long
- score_of_player(long player)
- {
- insure_game();
- return player_score(&real_state, real_player(player));
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function returns TRUE if the specified player has won the game, **/
- /** and FALSE otherwise. **/
- /** **/
- /****************************************************************************/
-
- Boolean
- is_winner(long player)
- {
- insure_game();
- return winner(&real_state, player);
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function returns TRUE if the board is completely full, FALSE **/
- /** otherwise. **/
- /** **/
- /****************************************************************************/
-
- Boolean
- is_tie()
- {
- insure_game();
- return tie(&real_state);
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function returns the coordinates of the winning connections of **/
- /** the specified player. It is assumed that the specified player has **/
- /** indeed won the game. The coordinates are returned in x1, y1, x2, y2, **/
- /** where (x1, y1) specifies the lower-left piece of the winning **/
- /** connection, and (x2, y2) specifies the upper-right piece of the **/
- /** winning connection. If more than one winning connection exists, only **/
- /** one will be returned. **/
- /** **/
- /****************************************************************************/
-
- void
- win_coords(long player, long *x1, long *y1, long *x2, long *y2)
- {
- long i, j, win_pos = -1, look_for = 1 << num_to_connect;
- long realplayer;
- Boolean found;
-
- insure_game();
- realplayer = real_player(player);
- for (i=0; i<win_places; i++)
- if (real_state.score_array[realplayer][i] == look_for)
- win_pos = i;
-
- if (win_pos == -1) {
- fprintf(stderr, "win_coords: no winner\n");
- exit(EXIT_FAILURE);
- }
-
- /* Find the lower-left piece of the winning connection. */
-
- found = FALSE;
- for (j=0; j<size_y; j++)
- for (i=0; i<size_x; i++)
- if (map[i][j][win_pos] && !found) {
- *x1 = i;
- *y1 = j;
- found = TRUE;
- }
-
- /* Find the upper-right piece of the winning connection. */
-
- found = FALSE;
- for (j=size_y-1; j>=0; j--)
- for (i=size_x-1; i>=0; i--)
- if (map[i][j][win_pos] && !found) {
- *x2 = i;
- *y2 = j;
- found = TRUE;
- }
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function ends the current game. It is assumed that a game is **/
- /** indeed in progress. It is illegal to call any other game function **/
- /** immediately after this one except for new_game() and poll(). **/
- /** **/
- /****************************************************************************/
-
- void
- end_game(void)
- {
- long i, j;
-
- insure_game();
-
- /* Free up the memory used by the game state. */
-
- for (i=0; i<size_x; i++) free(real_state.board[i]);
- free(real_state.board);
- free(real_state.score_array[0]);
- free(real_state.score_array[1]);
-
- /* Free up the memory used by the map. */
-
- for (i=0; i<size_x; i++) {
- for (j=0; j<size_y; j++)
- free(map[i][j]);
- free(map[i]);
- }
- free(map);
-
- /* Free up the memory of all the temporary states used. */
-
- for (i=0; i<temp_states_allocated; i++) {
- temp_array[i] = FALSE;
- for (j=0; j<size_x; j++) free(temp_states[i].board[j]);
- free(temp_states[i].board);
- free(temp_states[i].score_array[0]);
- free(temp_states[i].score_array[1]);
- }
- temp_states_allocated = 0;
-
- game_in_progress = FALSE;
- }
-
-
- /****************************************************************************/
- /****************************************************************************/
- /** **/
- /** The following functions are local to this file and should not be **/
- /** called externally. **/
- /** **/
- /****************************************************************************/
- /****************************************************************************/
-
-
- /****************************************************************************/
- /** **/
- /** This function insures that a game is in progress, and exits with an **/
- /** error if one is not. **/
- /** **/
- /****************************************************************************/
-
- static void
- insure_game(void)
- {
- if (!game_in_progress) {
- fprintf(stderr, "error: no game in progress\n");
- exit(EXIT_FAILURE);
- }
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function returns the number of possible win positions on a board **/
- /** of dimensions x by y with n being the number of pieces required in a **/
- /** row in order to win. **/
- /** **/
- /****************************************************************************/
-
- static long
- num_of_win_places(long x, long y, long n)
- {
- return 4*x*y - 3*x*n - 3*y*n + 3*x + 3*y - 4*n + 2*n*n + 2;
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function updates the score of the specified player given that **/
- /** the player has just placed a game piece in column x, row y. **/
- /** **/
- /** The specified game state is used, which may be a temporary state. **/
- /** **/
- /****************************************************************************/
-
- static void
- update_score(Game_state *state, long player, long x, long y)
- {
- long i;
-
- for (i=0; i<win_places; i++)
- if (map[x][y][i]) {
- state->score_array[player][i] <<= 1;
- state->score_array[other(player)][i] = 0;
- }
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function drops a piece of the specified player into the **/
- /** specified column. A value of TRUE is returned if the drop was **/
- /** successful, and FALSE if it was not (i.e. the specified column is **/
- /** full). **/
- /** **/
- /** The specified game state is used, which may be a temporary state. **/
- /** **/
- /****************************************************************************/
-
- static Boolean
- drop_piece(Game_state *state, long player, long column)
- {
- long y = 0;
-
- while (state->board[column][y] != EMPTY && ++y < size_y)
- ;
-
- if (y == size_y) return FALSE;
-
- state->board[column][y] = player;
- state->num_of_pieces++;
- update_score(state, player, column, y);
-
- return TRUE;
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function returns the "score" of the specified player. This **/
- /** score is a function of how many winning positions are still available **/
- /** to the player and how close he/she is to achieving each of these **/
- /** positions. The scores of both players can be compared to observe how **/
- /** well they are doing relative to each other. **/
- /** **/
- /** The specified game state is used, which may be a temporary state. **/
- /** **/
- /****************************************************************************/
-
- static long
- player_score(Game_state *state, long player)
- {
- long i, score = 0;
-
- for (i=0; i<win_places; i++)
- score += state->score_array[player][i];
-
- return score;
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function returns TRUE if the specified player has won the game, **/
- /** and FALSE otherwise. **/
- /** **/
- /** The specified game state is used, which may be a temporary state. **/
- /** **/
- /****************************************************************************/
-
- static Boolean
- winner(Game_state *state, long player)
- {
- long i, look_for = 1 << num_to_connect;
-
- for (i=0; i<win_places; i++)
- if (state->score_array[player][i] == look_for)
- return TRUE;
-
- return FALSE;
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function returns TRUE if the board is completely full, FALSE **/
- /** otherwise. **/
- /** **/
- /** The specified game state is used, which may be a temporary state. **/
- /** **/
- /****************************************************************************/
-
- static Boolean
- tie(Game_state *state)
- {
- return (state->num_of_pieces == size_x * size_y);
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function returns a measure of the "goodness" of the specified **/
- /** state for the specified player. This goodness value is calculated by **/
- /** subtracting the score of the player's opponent from the player's own **/
- /** score. A positive value will result if the specified player is in a **/
- /** better situation than his/her opponent. **/
- /** **/
- /****************************************************************************/
-
- static long
- goodness_of(Game_state *state, long player)
- {
- return player_score(state, player) - player_score(state, other(player));
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function will return a copy of the specified state. For **/
- /** efficiency, an array of allocated temporary states is kept, so that **/
- /** memory does not have to be allocated for a new state every time this **/
- /** function is called. When the copy is finished with, it must be **/
- /** destroyed with destroy_state(). **/
- /** **/
- /****************************************************************************/
-
- static Game_state *
- copy_state(Game_state *state)
- {
- long i, j, k;
-
- for (i=0; i<temp_states_allocated; i++)
- if(!temp_array[i]) break;
-
- if (i==temp_states_allocated) {
- if (i==NUM_OF_TEMP_STATES) {
- fprintf(stderr, "copy_state: too many temp states\n");
- exit(EXIT_FAILURE);
- }
-
- /* Allocate space for the board */
-
- temp_states[i].board = (char **) emalloc(size_x * sizeof(char *));
- for (j=0; j<size_x; j++)
- temp_states[i].board[j] = (char *) emalloc(size_y * sizeof(char));
-
- /* Allocate space for the score array */
-
- temp_states[i].score_array[0] =
- (long *) emalloc(win_places * sizeof(long));
- temp_states[i].score_array[1] =
- (long *) emalloc(win_places * sizeof(long));
-
- temp_states_allocated++;
- }
-
- temp_array[i] = TRUE;
-
- /* Copy the board */
-
- for (j=0; j<size_x; j++)
- for (k=0; k<size_y; k++)
- temp_states[i].board[j][k] = state->board[j][k];
-
- /* Copy the score array */
-
- for (j=0; j<win_places; j++) {
- temp_states[i].score_array[0][j] = state->score_array[0][j];
- temp_states[i].score_array[1][j] = state->score_array[1][j];
- }
-
- return &temp_states[i];
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function destroys the specified game state (assumed to have been **/
- /** created by copy_state()). For efficiency, the memory used by the **/
- /** state is not deallocated, so that copy_state() may use this memory **/
- /** again for a new state. **/
- /** **/
- /****************************************************************************/
-
- static void
- destroy_state(Game_state *state)
- {
- long i, j;
-
- for (i=0; i<temp_states_allocated; i++)
- if(state == &temp_states[i]) break;
-
- if (i==temp_states_allocated) {
- fprintf(stderr, "destroy_state: state doesn't exist\n");
- exit(EXIT_FAILURE);
- }
-
- temp_array[i] = FALSE;
- }
-
-
- /****************************************************************************/
- /** **/
- /** This function determines how good the specified state may turn out to **/
- /** be for the specified player. It does this by looking ahead level **/
- /** moves. It is assumed that both the specified player and the opponent **/
- /** may make the best move possible. Since this function is recursive, **/
- /** depth keeps track of the current depth of recursion. so_far keeps **/
- /** track of the best worst goodness (if you dig my meaning) so far, so **/
- /** that if a goodness worse than that crops up, the game tree can be **/
- /** pruned to avoid searching unneccessary paths (this technique is **/
- /** called alpha-beta cutoff). **/
- /** **/
- /** The specified poll function (if any) is called at the appropriate **/
- /** level. **/
- /** **/
- /** The worst goodness that the specified state can produce in the number **/
- /** of moves (levels) searched is returned. This is the best the **/
- /** specified player can hope to achieve with this state (since it is **/
- /** assumed that the opponent will make the best moves possible). **/
- /** **/
- /****************************************************************************/
-
- static long
- worst_goodness(Game_state *state, long player, long level, long depth,
- long so_far)
- {
- long i, goodness, best;
- Game_state *temp_state;
-
- if (poll_function && level-depth==poll_level) (*poll_function)();
- if (level == depth)
- return goodness_of(state, player);
- else {
- /* Assume it is the other player's turn. */
- best = -2000000;
- for(i=0; i<size_x; i++) {
- temp_state = copy_state(state);
- if (!drop_piece(temp_state, other(player), i)) {
- destroy_state(temp_state);
- continue;
- }
- if (winner(temp_state, other(player)))
- goodness = 1000000 - depth;
- else
- goodness = worst_goodness(temp_state, other(player), level,
- depth+1, best);
- if (goodness > best)
- best = goodness;
- destroy_state(temp_state);
- if (-best < so_far) break;
- }
-
- /* What's good for the other player is bad for this one. */
- return -best;
- }
- }
-
-
- /****************************************************************************/
- /** **/
- /** A safer version of malloc(). **/
- /** **/
- /****************************************************************************/
-
- void *
- emalloc(unsigned int n)
- {
- void *ptr;
-
- ptr = (void *) malloc(n);
- if ( ptr == NULL ) {
- fprintf(stderr,"c4: emalloc() - Can't allocate %d bytes.\n", n);
- exit(EXIT_FAILURE);
- }
- return ptr;
- }
-
-
-